// End-to-end integration tests for the complete file download workflow
// This test simulates the exact web UI behavior to ensure downloads work correctly

use serde_json::Value;
use std::sync::Arc;
use std::time::Duration;
use tempfile::TempDir;
use tokio::time::timeout;

use axum::{
    extract::DefaultBodyLimit,
    http::Method,
    routing::{delete, get, post, put},
    Router,
};
use rdm_rust::config::AppConfig;
use rdm_rust::database::connection::establish_connection_pool;
use rdm_rust::handlers::{auth, data};
use rdm_rust::services::{
    AuthService, DataService, DvcMetadataService, DvcService, StorageService,
};
use rdm_rust::AppState;
use reqwest::multipart;
use tower::ServiceBuilder;
use tower_http::{
    cors::{Any, CorsLayer},
    trace::TraceLayer,
};

// Test configuration
const TEST_SERVER_PORT: u16 = 0; // Use random available port
const TEST_TIMEOUT: Duration = Duration::from_secs(30);

struct TestServer {
    port: u16,
    temp_dir: TempDir,
    base_url: String,
    client: reqwest::Client,
}

impl TestServer {
    async fn new() -> Self {
        // Create temporary directory for test storage
        let temp_dir = TempDir::new().expect("Failed to create temp dir");

        // Load test configuration
        let mut config = AppConfig::from_env().expect("Failed to load config");
        config.server.port = TEST_SERVER_PORT;
        config.dvc.storage_path = temp_dir.path().to_string_lossy().to_string();

        // Initialize database
        let db_pool = establish_connection_pool(&config.database.url)
            .await
            .expect("Failed to establish database connection");

        // Initialize services
        let auth_service = Arc::new(AuthService::new(db_pool.clone(), config.clone()));
        let data_service = Arc::new(DataService::new(db_pool.clone()));
        let storage_service = Arc::new(StorageService::new(config.dvc.storage_path.clone()));
        let dvc_service = Arc::new(DvcService::new(db_pool.clone()));
        let dvc_metadata_service = Arc::new(DvcMetadataService::new(db_pool.clone()));

        let app_state = AppState {
            auth_service,
            data_service,
            storage_service,
            dvc_metadata_service,
            dvc_service,
        };

        // Build application router (same as main.rs)
        let public_routes = Router::new()
            .route("/api/register", post(auth::register))
            .route("/api/register-admin", post(auth::register_admin))
            .route("/api/login", post(auth::login))
            .route("/api/data/public", get(data::list_public_data_items));

        let protected_routes = Router::new()
            .route("/api/user-info", get(auth::user_info))
            .route("/api/data", get(data::list_data_items))
            .route("/api/data/upload", post(data::upload_data))
            .route("/api/data/:id", get(data::get_data_item))
            .route("/api/data/:id", delete(data::delete_data_item))
            .route("/api/data/:id/download", get(data::download_data_file))
            .route("/api/data/:id/dvc", get(data::download_dvc_file))
            .route("/api/data/:id/dvc/content", get(data::get_dvc_content))
            .route("/api/data/:id/metadata", put(data::upload_metadata))
            .route_layer(axum::middleware::from_fn_with_state(
                app_state.clone(),
                rdm_rust::middleware::auth::auth_middleware,
            ));

        let app = Router::new()
            .merge(public_routes)
            .merge(protected_routes)
            .layer(
                ServiceBuilder::new()
                    .layer(TraceLayer::new_for_http())
                    .layer(
                        CorsLayer::new()
                            .allow_origin(Any)
                            .allow_methods([
                                Method::GET,
                                Method::POST,
                                Method::PUT,
                                Method::DELETE,
                                Method::OPTIONS,
                            ])
                            .allow_headers(Any),
                    )
                    .layer(DefaultBodyLimit::max(100 * 1024 * 1024)), // 100MB max file size
            )
            .with_state(app_state);

        // Start server on random port
        let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
            .await
            .expect("Failed to bind to address");
        let port = listener
            .local_addr()
            .expect("Failed to get local address")
            .port();

        // Spawn server task
        tokio::spawn(async move {
            axum::serve(listener, app)
                .await
                .expect("Failed to serve server");
        });

        // Give server time to start
        tokio::time::sleep(Duration::from_millis(100)).await;

        let base_url = format!("http://127.0.0.1:{}", port);
        let client = reqwest::Client::builder()
            .timeout(TEST_TIMEOUT)
            .build()
            .expect("Failed to create HTTP client");

        Self {
            port,
            temp_dir,
            base_url,
            client,
        }
    }

    async fn wait_for_server(&self) -> Result<(), Box<dyn std::error::Error>> {
        let health_url = format!("{}/api/data/public", self.base_url);

        for _ in 0..10 {
            match timeout(Duration::from_secs(1), self.client.get(&health_url).send()).await {
                Ok(Ok(response)) if response.status().is_success() => return Ok(()),
                Ok(_) => (),
                Err(_) => (),
            }
            tokio::time::sleep(Duration::from_millis(100)).await;
        }

        Err("Server failed to start".into())
    }

    async fn register_and_login(&self, email: &str, password: &str) -> String {
        // Register user
        let register_response = self
            .client
            .post(format!("{}/api/register", self.base_url))
            .json(&serde_json::json!({
                "email": email,
                "password": password
            }))
            .send()
            .await
            .expect("Failed to send register request");

        assert!(
            register_response.status().is_success(),
            "User registration should succeed"
        );

        // Login to get auth token
        let login_response = self
            .client
            .post(format!("{}/api/login", self.base_url))
            .json(&serde_json::json!({
                "email": email,
                "password": password
            }))
            .send()
            .await
            .expect("Failed to send login request");

        assert_eq!(login_response.status(), 200, "Login should succeed");

        let login_body: Value = login_response
            .json()
            .await
            .expect("Failed to parse login response");
        let auth_token = login_body
            .get("access_token")
            .or_else(|| login_body.get("token"))
            .and_then(|t| t.as_str())
            .expect("Login response should contain token");

        auth_token.to_string()
    }

    async fn upload_test_file(
        &self,
        auth_token: &str,
        filename: &str,
        content: &[u8],
        project: String,
    ) -> i64 {
        let form = multipart::Form::new()
            .part(
                "files",
                multipart::Part::bytes(content.to_vec())
                    .file_name(filename.to_string())
                    .mime_str("text/plain")
                    .expect("Invalid mime type"),
            )
            .text("project", project)
            .text("source", "test://download-test")
            .text("description", "Test file for download integration tests");

        let upload_response = self
            .client
            .post(format!("{}/api/data/upload", self.base_url))
            .header("Authorization", format!("Bearer {}", auth_token))
            .multipart(form)
            .send()
            .await
            .expect("Failed to send upload request");

        assert_eq!(upload_response.status(), 200, "File upload should succeed");

        let upload_body: Value = upload_response
            .json()
            .await
            .expect("Failed to parse upload response");
        let uploaded_items = upload_body.get("items").unwrap().as_array().unwrap();
        assert_eq!(uploaded_items.len(), 1);

        let uploaded_item = &uploaded_items[0];
        uploaded_item.get("id").unwrap().as_i64().unwrap()
    }
}

#[tokio::test]
async fn test_end_to_end_download_workflow() {
    let server = TestServer::new().await;
    server
        .wait_for_server()
        .await
        .expect("Test server failed to start");

    println!("Test server running on: {}", server.base_url);

    // Setup: Register and login user
    let test_email = format!(
        "downloadtest{}@hillstonenet.com",
        chrono::Utc::now().timestamp_nanos_opt().unwrap_or(0)
    );
    let test_password = "testpassword123";
    let auth_token = server.register_and_login(&test_email, test_password).await;

    // Setup: Upload test file
    let test_content = b"This is a test file for download verification.\nIt contains multiple lines.\nEnd of test.";
    let test_filename = "test_download_file.txt";
    let item_id = server
        .upload_test_file(
            &auth_token,
            test_filename,
            test_content,
            "Download Test Project".to_string(),
        )
        .await;

    println!("Uploaded test file: {} (ID: {})", test_filename, item_id);

    // Test 1: Download the actual file
    let download_response = server
        .client
        .get(format!("{}/api/data/{}/download", server.base_url, item_id))
        .header("Authorization", format!("Bearer {}", auth_token))
        .send()
        .await
        .expect("Failed to send download request");

    assert_eq!(
        download_response.status(),
        200,
        "File download should succeed"
    );

    // Check response headers
    let content_type = download_response.headers().get("content-type").unwrap();
    assert_eq!(content_type, "application/octet-stream");

    let content_disposition = download_response
        .headers()
        .get("content-disposition")
        .unwrap();
    println!(
        "Content-Disposition: {}",
        content_disposition.to_str().unwrap()
    );
    assert!(content_disposition.to_str().unwrap().contains("attachment"));
    println!(
        "Content-Disposition: {}",
        content_disposition.to_str().unwrap()
    );
    assert!(content_disposition.to_str().unwrap().contains("attachment"));

    let downloaded_content = download_response
        .bytes()
        .await
        .expect("Failed to read downloaded content");
    assert_eq!(
        downloaded_content.as_ref(),
        test_content,
        "Downloaded content should match original"
    );

    println!("✓ File download test passed");

    // Test 2: Download DVC file
    let dvc_response = server
        .client
        .get(format!("{}/api/data/{}/dvc", server.base_url, item_id))
        .header("Authorization", format!("Bearer {}", auth_token))
        .send()
        .await
        .expect("Failed to send DVC download request");

    assert_eq!(
        dvc_response.status(),
        200,
        "DVC file download should succeed"
    );

    // Check DVC response headers
    let dvc_content_type = dvc_response.headers().get("content-type").unwrap();
    assert_eq!(dvc_content_type, "text/yaml");

    let dvc_content_disposition = dvc_response.headers().get("content-disposition").unwrap();
    assert!(dvc_content_disposition
        .to_str()
        .unwrap()
        .contains("attachment"));
    assert!(dvc_content_disposition
        .to_str()
        .unwrap()
        .contains("data.dvc"));

    let dvc_content = dvc_response
        .text()
        .await
        .expect("Failed to read DVC content");
    assert!(!dvc_content.is_empty(), "DVC content should not be empty");
    assert!(
        dvc_content.contains("outs:"),
        "DVC content should contain outs section"
    );
    assert!(
        dvc_content.contains("md5:"),
        "DVC content should contain md5 hash"
    );
    assert!(
        dvc_content.contains("path:"),
        "DVC content should contain file path"
    );

    println!("DVC content: {}", dvc_content);
    println!("✓ DVC file download test passed");

    // Test 3: Get DVC content as JSON
    let dvc_json_response = server
        .client
        .get(format!(
            "{}/api/data/{}/dvc/content",
            server.base_url, item_id
        ))
        .header("Authorization", format!("Bearer {}", auth_token))
        .send()
        .await
        .expect("Failed to send DVC content request");

    assert_eq!(
        dvc_json_response.status(),
        200,
        "DVC content should succeed"
    );

    let dvc_content = dvc_json_response
        .text()
        .await
        .expect("Failed to get DVC content response");

    // The DVC content should be returned as YAML text
    println!("DVC content response: {}", dvc_content);

    // Verify that the content contains expected YAML structure
    assert!(
        dvc_content.contains("outs:"),
        "DVC content should contain outs:"
    );
    assert!(
        dvc_content.contains("md5:"),
        "DVC content should contain md5:"
    );
    assert!(
        dvc_content.contains("path:"),
        "DVC content should contain path:"
    );
    assert!(
        dvc_content.contains("cache:"),
        "DVC content should contain cache:"
    );

    // Verify that it contains the expected filename
    assert!(
        dvc_content.contains("test_dvc_json.txt"),
        "DVC content should contain the filename"
    );

    println!("DVC JSON: {}", dvc_content);
    println!("✓ DVC content JSON test passed");

    // Cleanup: Delete the test file
    let delete_response = server
        .client
        .delete(format!("{}/api/data/{}", server.base_url, item_id))
        .header("Authorization", format!("Bearer {}", auth_token))
        .send()
        .await
        .expect("Failed to send delete request");

    assert_eq!(
        delete_response.status(),
        204,
        "File deletion should succeed"
    );
    println!("✓ Cleanup completed");
}

#[tokio::test]
async fn test_download_unauthorized_access() {
    let server = TestServer::new().await;
    server
        .wait_for_server()
        .await
        .expect("Test server failed to start");

    // Setup: Register user and upload file
    let test_email = "unauthorizedtest{}@hillstonenet.com";
    let test_password = "password123";
    let auth_token = server.register_and_login(test_email, test_password).await;

    let test_content = b"Unauthorized access test content";
    let item_id = server
        .upload_test_file(
            &auth_token,
            "unauthorized_test.txt",
            test_content,
            "Unauthorized Test".to_string(),
        )
        .await;

    // Test: Try to download without authentication
    let download_response = server
        .client
        .get(format!("{}/api/data/{}/download", server.base_url, item_id))
        .send()
        .await
        .expect("Failed to send unauthorized download request");

    assert_eq!(
        download_response.status(),
        401,
        "Download without auth should fail"
    );

    // Test: Try to download DVC file without authentication
    let dvc_response = server
        .client
        .get(format!("{}/api/data/{}/dvc", server.base_url, item_id))
        .send()
        .await
        .expect("Failed to send unauthorized DVC request");

    assert_eq!(
        dvc_response.status(),
        401,
        "DVC download without auth should fail"
    );

    // Test: Try to get DVC content without authentication
    let dvc_json_response = server
        .client
        .get(format!(
            "{}/api/data/{}/dvc/content",
            server.base_url, item_id
        ))
        .send()
        .await
        .expect("Failed to send unauthorized DVC JSON request");

    assert_eq!(
        dvc_json_response.status(),
        401,
        "DVC JSON without auth should fail"
    );

    println!("✓ Unauthorized access tests passed");
}

#[tokio::test]
async fn test_download_nonexistent_file() {
    let server = TestServer::new().await;
    server
        .wait_for_server()
        .await
        .expect("Test server failed to start");

    // Setup: Register and login user
    let test_email = "nonexistenttest{}@hillstonenet.com";
    let test_password = "password123";
    let auth_token = server.register_and_login(test_email, test_password).await;

    let nonexistent_id = 99999i64;

    // Test: Try to download non-existent file
    let download_response = server
        .client
        .get(format!(
            "{}/api/data/{}/download",
            server.base_url, nonexistent_id
        ))
        .header("Authorization", format!("Bearer {}", auth_token))
        .send()
        .await
        .expect("Failed to send download request for non-existent file");

    assert_eq!(
        download_response.status(),
        404,
        "Download of non-existent file should return 404"
    );

    // Test: Try to download DVC file for non-existent file
    let dvc_response = server
        .client
        .get(format!(
            "{}/api/data/{}/dvc",
            server.base_url, nonexistent_id
        ))
        .header("Authorization", format!("Bearer {}", auth_token))
        .send()
        .await
        .expect("Failed to send DVC request for non-existent file");

    assert_eq!(
        dvc_response.status(),
        404,
        "DVC download of non-existent file should return 404"
    );

    // Test: Try to get DVC content for non-existent file
    let dvc_json_response = server
        .client
        .get(format!(
            "{}/api/data/{}/dvc/content",
            server.base_url, nonexistent_id
        ))
        .header("Authorization", format!("Bearer {}", auth_token))
        .send()
        .await
        .expect("Failed to send DVC JSON request for non-existent file");

    assert_eq!(
        dvc_json_response.status(),
        404,
        "DVC JSON of non-existent file should return 404"
    );

    println!("✓ Non-existent file tests passed");
}

#[tokio::test]
async fn test_download_different_file_types() {
    let server = TestServer::new().await;
    server
        .wait_for_server()
        .await
        .expect("Test server failed to start");

    // Setup: Register and login user
    let test_email = "filetypestest{}@hillstonenet.com";
    let test_password = "password123";
    let auth_token = server.register_and_login(test_email, test_password).await;

    // Test 1: Text file
    let text_content =
        b"This is a plain text file\nWith multiple lines\nAnd some special chars: aaa";
    let text_id = server
        .upload_test_file(
            &auth_token,
            "test.txt",
            text_content,
            "File Types Test".to_string(),
        )
        .await;

    let text_download = server
        .client
        .get(format!("{}/api/data/{}/download", server.base_url, text_id))
        .header("Authorization", format!("Bearer {}", auth_token))
        .send()
        .await
        .expect("Failed to download text file");

    assert_eq!(text_download.status(), 200);
    let downloaded_text = text_download
        .bytes()
        .await
        .expect("Failed to read text content");
    assert_eq!(downloaded_text.as_ref(), text_content);

    // Test 2: Binary file (simulated image)
    let binary_content = vec![0u8; 1024]; // 1KB of zeros
    let binary_form = multipart::Form::new()
        .part(
            "files",
            multipart::Part::bytes(binary_content.clone())
                .file_name("test.bin")
                .mime_str("application/octet-stream")
                .expect("Invalid mime type"),
        )
        .text("project", "File Types Test")
        .text("source", "test://file-types")
        .text("description", "Binary file test");

    let binary_upload = server
        .client
        .post(format!("{}/api/data/upload", server.base_url))
        .header("Authorization", format!("Bearer {}", auth_token))
        .multipart(binary_form)
        .send()
        .await
        .expect("Failed to upload binary file");

    assert_eq!(binary_upload.status(), 200);
    let binary_upload_body: Value = binary_upload
        .json()
        .await
        .expect("Failed to parse binary upload response");
    let binary_items = binary_upload_body.get("items").unwrap().as_array().unwrap();
    let binary_id = binary_items[0].get("id").unwrap().as_i64().unwrap();

    let binary_download = server
        .client
        .get(format!(
            "{}/api/data/{}/download",
            server.base_url, binary_id
        ))
        .header("Authorization", format!("Bearer {}", auth_token))
        .send()
        .await
        .expect("Failed to download binary file");

    assert_eq!(binary_download.status(), 200);
    let downloaded_binary = binary_download
        .bytes()
        .await
        .expect("Failed to read binary content");
    assert_eq!(downloaded_binary.as_ref(), binary_content.as_slice());

    // Test 3: Large file
    let large_content = vec![42u8; 100 * 1024]; // 100KB
    let large_form = multipart::Form::new()
        .part(
            "files",
            multipart::Part::bytes(large_content.clone())
                .file_name("large_file.dat")
                .mime_str("application/octet-stream")
                .expect("Invalid mime type"),
        )
        .text("project", "File Types Test")
        .text("source", "test://file-types")
        .text("description", "Large file test");

    let large_upload = server
        .client
        .post(format!("{}/api/data/upload", server.base_url))
        .header("Authorization", format!("Bearer {}", auth_token))
        .multipart(large_form)
        .send()
        .await
        .expect("Failed to upload large file");

    assert_eq!(large_upload.status(), 200);
    let large_upload_body: Value = large_upload
        .json()
        .await
        .expect("Failed to parse large upload response");
    let large_items = large_upload_body.get("items").unwrap().as_array().unwrap();
    let large_id = large_items[0].get("id").unwrap().as_i64().unwrap();

    let large_download = server
        .client
        .get(format!(
            "{}/api/data/{}/download",
            server.base_url, large_id
        ))
        .header("Authorization", format!("Bearer {}", auth_token))
        .send()
        .await
        .expect("Failed to download large file");

    assert_eq!(large_download.status(), 200);
    let downloaded_large = large_download
        .bytes()
        .await
        .expect("Failed to read large content");
    assert_eq!(downloaded_large.as_ref(), large_content.as_slice());

    println!("✓ Different file types tests passed");
}

#[tokio::test]
async fn test_public_vs_private_file_access() {
    let server = TestServer::new().await;
    server
        .wait_for_server()
        .await
        .expect("Test server failed to start");

    // Setup: Register two users
    let user1_email = "user1{}@hillstonenet.com";
    let user1_password = "password123";
    let user1_token = server.register_and_login(user1_email, user1_password).await;

    let user2_email = "user2{}@hillstonenet.com";
    let user2_password = "password123";
    let user2_token = server.register_and_login(user2_email, user2_password).await;

    // User 1 uploads a file (public by default based on upload handler)
    let test_content = b"Public vs private test content";
    let item_id = server
        .upload_test_file(
            &user1_token,
            "access_test.txt",
            test_content,
            "Access Test".to_string(),
        )
        .await;

    // Test 1: User 1 can download their own file
    let user1_download = server
        .client
        .get(format!("{}/api/data/{}/download", server.base_url, item_id))
        .header("Authorization", format!("Bearer {}", user1_token))
        .send()
        .await
        .expect("Failed to download with user1 token");

    assert_eq!(
        user1_download.status(),
        200,
        "User should be able to download their own file"
    );

    // Test 2: User 2 can download User 1's file (because it's public)
    let user2_download = server
        .client
        .get(format!("{}/api/data/{}/download", server.base_url, item_id))
        .header("Authorization", format!("Bearer {}", user2_token))
        .send()
        .await
        .expect("Failed to download with user2 token");

    assert_eq!(
        user2_download.status(),
        200,
        "User should be able to download public files from other users"
    );

    // Test 3: Same access rules apply to DVC downloads
    let user1_dvc = server
        .client
        .get(format!("{}/api/data/{}/dvc", server.base_url, item_id))
        .header("Authorization", format!("Bearer {}", user1_token))
        .send()
        .await
        .expect("Failed to get DVC with user1 token");

    assert_eq!(
        user1_dvc.status(),
        200,
        "User should be able to get their own DVC file"
    );

    let user2_dvc = server
        .client
        .get(format!("{}/api/data/{}/dvc", server.base_url, item_id))
        .header("Authorization", format!("Bearer {}", user2_token))
        .send()
        .await
        .expect("Failed to get DVC with user2 token");

    assert_eq!(
        user2_dvc.status(),
        200,
        "User should be able to get DVC files from other users if public"
    );

    println!("✓ Public vs private access tests passed");
}

#[tokio::test]
async fn test_dvc_content_structure() {
    let server = TestServer::new().await;
    server
        .wait_for_server()
        .await
        .expect("Test server failed to start");

    // Setup: Register and login user
    let test_email = "dvcstructuretest{}@hillstonenet.com";
    let test_password = "password123";
    let auth_token = server.register_and_login(test_email, test_password).await;

    // Upload a test file with known content
    let test_content = b"DVC structure test content with specific hash";
    let item_id = server
        .upload_test_file(
            &auth_token,
            "dvc_structure_test.txt",
            test_content,
            "DVC Structure Test".to_string(),
        )
        .await;

    // Get DVC content as JSON
    let dvc_json_response = server
        .client
        .get(format!(
            "{}/api/data/{}/dvc/content",
            server.base_url, item_id
        ))
        .header("Authorization", format!("Bearer {}", auth_token))
        .send()
        .await
        .expect("Failed to get DVC content");

    assert_eq!(dvc_json_response.status(), 200);

    let dvc_content = dvc_json_response
        .text()
        .await
        .expect("Failed to get DVC content");

    // Verify the content contains expected YAML structure
    println!("DVC content: {}", dvc_content);

    // Check for expected YAML structure
    assert!(
        dvc_content.contains("outs:"),
        "DVC content should contain outs:"
    );
    assert!(
        dvc_content.contains("md5:"),
        "DVC content should contain md5:"
    );
    assert!(
        dvc_content.contains("path:"),
        "DVC content should contain path:"
    );
    assert!(
        dvc_content.contains("cache:"),
        "DVC content should contain cache:"
    );
    assert!(
        dvc_content.contains("true"),
        "DVC content should contain cache: true"
    );

    // Verify that it contains the expected filename
    assert!(
        dvc_content.contains("dvc_structure_test.txt"),
        "DVC content should contain the filename"
    );

    // Also test the raw YAML download
    let dvc_yaml_response = server
        .client
        .get(format!("{}/api/data/{}/dvc", server.base_url, item_id))
        .header("Authorization", format!("Bearer {}", auth_token))
        .send()
        .await
        .expect("Failed to get DVC YAML");

    assert_eq!(dvc_yaml_response.status(), 200);

    let dvc_yaml = dvc_yaml_response
        .text()
        .await
        .expect("Failed to read DVC YAML");

    // Verify YAML structure
    assert!(
        dvc_yaml.contains("outs:"),
        "YAML should contain outs section"
    );
    assert!(dvc_yaml.contains("md5:"), "YAML should contain md5 field");
    assert!(dvc_yaml.contains("path:"), "YAML should contain path field");
    assert!(
        dvc_yaml.contains("cache: true"),
        "YAML should contain cache: true"
    );

    println!("DVC YAML:\n{}", dvc_yaml);
    println!("✓ DVC content structure tests passed");
}
